Julia - Obrada slike - Osnovi obrade slike

In [ ]:
#U okviru ovog notebook-a demonstriraćemo osnove rada sa oblasti obrade slike i njenih algoritama, odgovarajućih
#Julia paketa za obradu slike. Jednostavnosti radi, obrada slike će se uglavnom vršiti nad slikama prikazanim u 
#intenzitetima nijanse sive (grayscale), ali biće i primera gde će se obrada slike vršiti i nad slikama u boji (prelaz
#sa obrade slike prikazane i intenzitetima nijanse sive i slika u boji je jednostavan, i obrada se radi analogno,
#pa ćemo zbog jednostavnosti izlaganja, ali i interaktivnosti nekad raditi sa jednim tipovima slika, a nekada sa drugim tipovima slika)

#Julia ima odličnu, i sve više rastuću podršku za oblast obrade slika, 
#po performansama je mnogo brža od standardno funkcionalno bogatih programskih jezika
#kao što su Matlab i Python, a po performansama je veoma bliska jeziku C koji nema dobro razvijenu
#podršku za obradu slika, a u kojem se mnoge primene za oblasti obrade slike realizuju, pa na taj način Julia
#predstavlja sjajan kompromis između ovih naizgled suprotstavljenih zahteva

#Za pocetak cemo instalirati osnovne pakete za rad sa slikama koje ćemo koristiti:
import Pkg;
Pkg.add("TestImages")
   Updating registry at `~/.julia/registries/General`
    
   Updating git-repo `https://github.com/JuliaRegistries/General.git`
    Fetching: [============================>[1mFetching: [========================================>]  100.0 %
  Resolving package versions...
In [2]:
Pkg.add("Images")
In [3]:
Pkg.add("FileIO")
In [ ]:
Pkg.add("ImageShow")
In [7]:
using TestImages, Images, ImageShow 

#Slike u programskom jeziku Julia su nizovi - jednodimenzionalni i dvodimenzionalni (matrice)
#Oni se najčešće tretiraju i kao matrice, čija polja nazivamo pikselima - x osa naše slike 
#odgovara vrstama (smer porasta je odozgo nadole), a y osa naše slike 
#odgovara kolonama (smer porasta koordinata je sa levo na desno)

#za početak, izvršićemo učitavanje jedne slike iz paketa TestImages

img = testimage("cam");  #funkcijom testimage() kojoj kao argument prosleđujemo ime neke slike iz ovog paketa 
                       #vršimo učitavanje date slike u matricu koju nazivamo img 
                       #ukoliko se skloni operator ; dobiće se direktan ispis naše slike
Out[7]:
In [17]:
mosaicview(img)   #za 2-D slike funkcija koja se najčešće koristi za prikaz je funkcija mosaicview(), kojoj se kao
                  #ulazni argumenti dostavljaju slike koje želimo da prikažemo, uz opciono njihov raspored prikaza
                  #u okviru matrice
Out[17]:
In [18]:
#Ovakav interaktivni prikaz može se koristiti i za slike u boji:

img = testimage("barbara_color.png");
mosaicview(img)
Out[18]:
In [19]:
#Pre nego što nastavimo dalje, uvešćemo pojam maske (kernela). Maska predstavlja matricu koja je manjih dimenzija od
#slike koju obrađujemo, a koja služi za ekvivalentan prikaz matematičkih operacija koje vršimo nad našom slikom -
# filtriranje, računanje izvoda, detekcija i izdvajanje pojedinih regiona na slikama itd. Maska je najčešće neparnih
#dimenzija (ne mora nužno da bude, ali je ovakva konvencija jer se sve obrade koje vršimo 
#rade u odnosu na posmatrani piksel pa se teži ka tome da se svi pikseli oko
#našeg trenutnog piksela slike tretiraju simetrično u odnosu na njega)  U daljim obradama najčešće ćemo
#i raditi sa nekim predefinisanim, proporučenim maskama, i demonstriraćemo rad i rezultate
#dobijene sa njima

Filtriranje

In [20]:
#Filtriranje slike predstavlja osnovni i jedan od početnih koraka prilikom obrade svih slika - ono predstavlja uklanjanje
#smetnji (šumova), suvišnih detalja, redukcije uticaja pojedinih komponenti itd. sa ciljem da se dobija slika željenog
#kvaliteta nad kojima možemo vršiti kasnije obrade (izdvajanje karakterističnih regiona, mašinsko učenje nad slikom,
#primena nekog algoritma odlučivanja, detekcije ivica itd.)

#Filtriranje se može vršiti u prostornom i frekvencijskom domenu - obzirom na nivo izlaganja koji se ovde pruža i 
#njegovu složenost, a i sam matematički aparat koji je potreban kao predznanje za ovu oblast, ovde ćemo raditi isključivo
#obradu u prostornom domenu.

#Kako je naš trenutni cilj prostorno filtriranje nad slikom, za to nam je pre svega potrebna odgovarajuća maska.
#Ova maska će kliziti po našoj slici, i na taj način obraditi svaki piskel i dati njegovu izlaznu vrednost

#Problem će predstavljati pikseli na obodu slike - za njih nećemo imati dovoljan broj piksela u originalnoj slici jer
#će maska kada se oni postave u centar prelaziti granice naše slike - kao rešenje ovog problema uvodi se tehnika tzv.
#proširivanja (eng. padding) - vrši se proširivanje dimenzija naše slike dodatnim pikselima, koji se dopunjavaju tako da 
#arimtetičke operacije koje će biti realizovane maskom budu korektne i da se program nesmetano izvršava, a dopunjavaju
#se nekim karakterističnim vrednostima - najniži intenziteti piksela, najviši inteyiteti piksela, kopiranje vrednosti
#na granici itd.

#Ako posmatramo piskel na mestu (i,j) nad kojim vrsimo obradu, i on je u datom trenutku obrade u centru maske M koja
#je dimenzija m x n, onda će svaki piksel koji se nalazi u datoj masci morati da bude pomnožen sa vrednošću maske koja
#se poklapa sa njegovom pozicijom, i nakon toga će se sva ova množenja sabrati i upisati na mesto trenutnog piksela,
#sa eventualnim skaliranjem vrednosti na opseg na koji smo se odlučili da sa njim radimo/ograničeni samim programskim jezikom
#Ovo je postupak ako radimo sa grayscale slikama. Ukoliko se radi sa slikama u boji, onda je ideja da se one prestave
#u nekom kolor-sistemu koji barem kao jedan od svojih slojeva ima kao karakteristiku grayscale intentitete, intenziteta osvetljaja itd.
#pa se onda nad tim slojem radi upravo ista ovakva obrada (na početku je navedeno da je obrada ovakvih slika potpuno
#analogna sa obradom grayscale slika) ili se eventualno ova slika može obraditi tako da se navedena primena maske 
#primeni na sva tri sloja naše slike
In [2]:
#Za pocetak, proučićemo osnove filtra za usrednjavanje

#Filtar za usrednjavanje ima za cilj uklanjanje šuma koji je posledica visokih frekvencija uhvaćenih našom slikom,
#ili za eliminaciju veoma izraženih detalja na našoj slici tj. za cilj ima uprošćavanje slike (ovi detalji se takodje
#mogu interpretirati kao posledica prisustva visokih frekvencija u našoj slici). Ponekad, ovaj filtar se može
#koristiti i za zamućuvanje slike, ako nam je iz nekog razloga to potrebno.

#Ovakav filtar implementira se tako što se na sve njegove pozicije upiše koeficijent 1, a potom se izvrši deljenje svih
#elemenata matrice sa ukupnim brojem polja u matrici:

usr_filt = [1 1 1; 1 1 1; 1 1 1]
Out[2]:
3×3 Array{Int64,2}:
 1  1  1
 1  1  1
 1  1  1
In [4]:
(m, n)  = size(usr_filt)
Out[4]:
(3, 3)
In [5]:
usr_filt = usr_filt / (m*n)
Out[5]:
3×3 Array{Float64,2}:
 0.111111  0.111111  0.111111
 0.111111  0.111111  0.111111
 0.111111  0.111111  0.111111
In [10]:
#Sada cemo primeniti ovo na prvoj slici



img = testimage("cam");
rez_slika = imfilter( img, centered(usr_filt), "replicate"); #u osnovi, prosledjujemo sliku, kernel za filtriranje i tip paddovanja - funkcija koja vrsi filtriranje nase slike za datu masku
mosaicview(rez_slika)
Out[10]:
In [13]:
#sada cemo dati prikaz slika pre i posle filtriranja
mosaicview(img,rez_slika; nrow = 1)  #ova funkcija se može koristiti i za prikaz više slika radi neke uporedbe
Out[13]:
In [12]:
#Ono sta mozemo videti jeste da na ovoj slici nije bio prisutan nikakav vidan sum, niti izrazeni detalji,
#pa je ovde izvrseno samo blurr-ovanje slike
In [22]:
#Sada ćemo primeniti ovaj filtar na jednu sliku u boji koja ima izražene detalje, 
#sa ciljem potiskivanja ovih detalja
img = testimage("mandrill.tiff");
rez_slika = imfilter( img, centered(usr_filt), "symmetric");
mosaicview(img,rez_slika; nrow = 1)
Out[22]:
In [17]:
#Za usrednjavanje mogu se koristiti i druge maske - po pravilu, što je veći centralni koeficijent maske u 
#odnosu na okolne koeficijente, to će i trenutni
#piksel imati veći uticaj u rezultatu filtriranja. Kod običnog usrednjavanja važno je imati uvek 
#normalizaciju svih koeficijenata
#deljenjem sa ukupnim zbirom elemenata u matrici
usr_filt2 = [1 1 1; 1 4 1; 1 1 1];
usr_filt2 = usr_filt2 / (sum(usr_filt2));
rez_slika2 = imfilter( img, centered(usr_filt2), "symmetric");
mosaicview(img,rez_slika, rez_slika2; nrow = 1)
Out[17]:
In [33]:
#Naravno, ovaj filtar ne može se koristiti u svakoj situaciji - jer njegovo korišćenje može biti bez efekta, 
#a čak u nekim situacijama može i bespotrebno narušiti kvalitet naše slike, 
#kao u gornjem primeru. Ukoliko je šum koji napada našu sliku Aditivan normalan šum, onda usvajamo pretpostavku
#da je prava vrednost trenutnog piksela zapravo srednja vrednost signala dobijenog od originalnog signala
#kada se na njega superponira ovaj šum, a varijansa je neka vrednost koju ne znamo - 
#te stoga uvodimo pretpostavku o njenoj veličini pa testiramo našu pretpostavku ili pomoću nekog estimatora 
#pokušavamo da estimiramo varijansu ovog šuma.  U opštem slučaju, težinski koeficijent ovakvog filtra

# mask [ s , t ] = exp (-1 * (s^2 + t^2)/(2 * sigma^2) )

#gde će sigma predstavljati varijansu našeg šuma

#BITNO: 

#U velikom broju slučajeva mi ćemo moći da primenimo filtar Gausovog šuma, zato što će, na osnovu centralne granične teoreme
#više šumova koji napadaju našu sliku i koji su identično raspodeljeni težiti da imaju normalnu funkciju gustine
#verovatnoće. 

#Sada ćemo demonstrirati rad Gausovog filtra na sledećoj slici:

#Za ucitavanje slika koje koristimo na lokalnom računaru, koristićemo se paketom FileIO

using FileIO

img = load("gfilt.jpg");  #Funkciji load() se kao argument prosleđuje putanja do naše slike. Ukoliko se slika već 
                          #nalazi u našem radnom direktorijumu, onda je dovoljno proslediti samo ime slike

rez_sl1 = imfilter(img, centered(Kernel.gaussian(0.5)), "symmetric");  
#kao prvi argument prosleđujemo sliku, kao drugi kernel kome dostavljamo varijansu našeg šuma, 
#a kao treći možemo navesti tip padovanja koji želimo


mosaicview(img,rez_sl1; nrow = 1)
Out[33]:
In [34]:
#Kao što možemo videti, uspeli smo da potisnemo značajan deo šuma sa ove slike

#Problem sa ovim filtrom je taj što će, ako izaberemo preveliku varijansu šuma, on početi da narušava ivice naših 
# objekata (i detalje) , kao što možemo videti na sledećem prikazu:


rez_sl2 = imfilter(img, centered(Kernel.gaussian(8)), "symmetric");
rez_sl3 = imfilter(img, centered(Kernel.gaussian(15)), "symmetric");

mosaicview(img,rez_sl1, rez_sl2, rez_sl3; nrow = 1)

#Ovaj problem se može rešavati npr. nekim filtrom koji pravi kompromis između filtriranja i narušavanja ivica - 
#primer ovoga bi bio bilateralni filtar, koji ovde nećemo obraditi zbog složenosti matematičkih koncepata
#koji stoje iza njega ili da se, nakon primene ovog filtra, primeni neki 
#algoritam za izoštravanje ivica, što ćemo raditi u kasnijim lekcijama
Out[34]:
In [37]:
#Za kraj, obradićemo jedan specijalan slučaj šuma koji se često javlja u praksi - impulsni šum. 
#Impulsni šum predstavlja nasumicna zasićenja intenziteta piksela na 
#minimalne i maksimalne vrednosti opsega - i kao takav, nema determinističku prirodu,
#pa dosadašnji filtri ne bi imali efekat za njegovo uklanjanje, čak bi i dodatno narušili 
#kvalitet beć narušene slike.

#Za rešavanje ovog problema, uvodimo tzv. median filtar - on će , na osnovu veličine maske koja 
#mu bude data, pokupiti sve elemente oko centriranog piksela, naći njihovu medianu
#(na taj način će izvršiti eliminaciju i gornjih i donjih zasićenja piksela)
#i tako pročistiti sliku od impulsnog šuma

#U zavisnosti od količine šuma, moguće je da će ovaj filtar morati da bude upotrebljen više puta nad istom slikom.

#instalacija 
Pkg.add("ImageFiltering");
Pkg.add("Statistics")
ArgumentError: Package ImageFiltering not found in current path:
- Run `import Pkg; Pkg.add("ImageFiltering")` to install the ImageFiltering package.


Stacktrace:
 [1] require(::Module, ::Symbol) at ./loading.jl:892
 [2] top-level scope at In[37]:1
In [43]:
#Jedna mana ovog filtra jesta ta što on, kao i njegovi prethodnici, vrše narušavanje detalja slike - kao poboljšanje
#ovoga nameće se tzv. adaptivni median filtar, koji je potrebno više puta primeniti nad slikom nego 
#običan median filtar, ali on odliččno čuva
#detalje koji običan median filtar ne čuva kada se vrši filtriranje slike -
#čitaocu se ostavlja da samostalno istraži ovaj drugi tip filtra i uporedi njegove rezultate 
#sa rezultatima običnog median filtra koje ćemo prikazati ovde:

using ImageFiltering, Statistics

img = load("slika_impulsni_sum.jpg");

procis_sl1 = mapwindow(median!, img, (3, 3)); #primena median filtra, prvi argument je tip filtra, 
                                              #drugi je slika, a treći su dimenzije našeg filtra
mosaicview(img,procis_sl1; nrow = 1)          #mapwindow je generalisana funkcija koja omogućava da se
                                            #bilo koja funkcija primeni nad prozorom obuhvaćenim maskom za filtriranje
Out[43]:
In [ ]: